package com.bzchao.doublecamera;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Toast;

import com.bzchao.camera.R;
import com.bzchao.doublecamera.base.BaseActivity;
import com.bzchao.doublecamera.base.ErrorDialog;
import com.bzchao.doublecamera.utils.SizeUtils;
import com.bzchao.doublecamera.utils.TimeUtils;
import com.bzchao.doublecamera.utils.TimeWhere;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 单个相机的处理器
 */
public class CameraOneHandler {
    private static final String TAG = "CameraOneActivity";
    private static final String FRAGMENT_DIALOG = "dialog";

    private static BaseActivity baseActivity;

    private CameraManager manager;
    private CameraDevice mCameraDevice;

    /**
     * 当前相机的ID。
     */
    private String mCameraId;
    //当前相机预览视图
    private AutoFitTextureView mTextureView;
    private BitmapCallBack bitmapCallBack;

    //这几个属性是启动时才会创建
    private HandlerThread mHandlerThread;
    private Handler mHandler;
    private ImageReader mImageReader;
    private Size mPreviewSize;
    private int imageWith, imageHeight;
    /**
     * 一个信号量以防止应用程序在关闭相机之前退出。
     */
    private Semaphore mCameraOpenCloseLock;

    public CameraOneHandler(BaseActivity baseActivity, AutoFitTextureView mTextureView, CameraManager manager, String mCameraId, BitmapCallBack bitmapCallBack) {
        this.baseActivity = baseActivity;
        this.mTextureView = mTextureView;
        this.manager = manager;
        this.mCameraId = mCameraId;
        this.bitmapCallBack = bitmapCallBack;
        mCameraOpenCloseLock = new Semaphore(mCameraId.hashCode());
    }

    public void setViewSize(int with, int height) {
        this.imageWith = with;
        this.imageHeight = height;
    }


    /**
     * TextureView 监听
     */
    private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
            Log.e("SurfaceTextureListener", "onSurfaceTextureAvailable");
            //当TextureView可用的时候 打开预览摄像头
            Log.e(TAG, "width:" + width + "  height:" + height);
            //设置相机输出
            setUpCameraOutputs(width, height);
            configureTransform(mTextureView.getWidth(), mTextureView.getHeight(), mTextureView, mPreviewSize);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture texture) {
        }

    };

    private CameraDevice.StateCallback getStateCallback() {
        return new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
                // 打开相机时调用此方法。 在这里开始相机预览。
                mCameraOpenCloseLock.release();
                mCameraDevice = cameraDevice;
                //创建CameraPreviewSession
                createCameraPreviewSession(mTextureView, mPreviewSize, mCameraDevice, mImageReader, mHandler);
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                mCameraOpenCloseLock.release();
                cameraDevice.close();
                mCameraDevice = null;
            }

            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int error) {
                mCameraOpenCloseLock.release();
                cameraDevice.close();
                mCameraDevice = null;
            }
        };

    }


    /**
     * 处理与JPEG捕获有关的事件
     */
    private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                        @NonNull CaptureRequest request,
                                        @NonNull CaptureResult partialResult) {
        }

        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                       @NonNull CaptureRequest request,
                                       @NonNull TotalCaptureResult result) {
        }
    };

    /**
     * 初始化
     */
    public void initView() {
        if (mHandlerThread == null) {
            startHandlerThread();
            setCameraView(mTextureView);
            openCameraOne();
        } else {
            stopHandlerThread();
        }
    }

    private void setCameraView(AutoFitTextureView mTextureView) {
        if (mTextureView.isAvailable()) {
            //设置相机输出
            setUpCameraOutputs(mTextureView.getWidth(), mTextureView.getHeight());
            configureTransform(mTextureView.getWidth(), mTextureView.getHeight(), mTextureView, mPreviewSize);
        } else {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }


    private void openCameraOne() {
        //检查权限
        if (ContextCompat.checkSelfPermission(baseActivity, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            requestCameraPermission();
            return;
        }
        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            //打开相机预览
            CameraDevice.StateCallback stateCallback = getStateCallback();
            manager.openCamera(mCameraId, stateCallback, mHandler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Configures the necessary {@link Matrix} transformation to `mTextureView`.
     * This method should be called after the camera preview size is determined in
     * setUpCameraOutputs and also the size of `mTextureView` is fixed.
     *
     * @param viewWidth  The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    private void configureTransform(int viewWidth, int viewHeight, AutoFitTextureView mTextureView, Size mPreviewSize) {
        if (null == mTextureView || null == mPreviewSize) {
            return;
        }
        int rotation = baseActivity.getWindowManager().getDefaultDisplay().getRotation();
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180, centerX, centerY);
        }
        mTextureView.setTransform(matrix);
    }

    /**
     * 设置与相机相关的成员变量。
     *
     * @param width  相机预览的可用尺寸宽度
     * @param height 相机预览的可用尺寸的高度
     */
    private void setUpCameraOutputs(int width, int height) {
        CameraManager manager = (CameraManager) baseActivity.getSystemService(Context.CAMERA_SERVICE);
        try {
            //获取相机的相关参数
            CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
            // 不使用前置摄像头。
            StreamConfigurationMap configurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            // 对于静态图像拍摄，使用最大的可用尺寸。
            Size largest = Collections.max(Arrays.asList(configurationMap.getOutputSizes(ImageFormat.JPEG)), new SizeUtils.CompareSizesByArea());
            Integer mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            //设置预览大小，mImageReader监听回调也是这个大小
            mImageReader = ImageReader.newInstance(imageWith, imageWith, ImageFormat.JPEG, 2);
            mPreviewSize = SizeUtils.getSize(baseActivity, configurationMap, mSensorOrientation, largest, width, height, mTextureView);
        } catch (CameraAccessException e) {
            Log.w("初始化摄像头失败", e);
        } catch (NullPointerException e) {
            Log.w("初始化摄像头失败", e);
            //不支持Camera2API
            ErrorDialog errorDialog = ErrorDialog.newInstance(baseActivity.getString(R.string.camera_error));
            errorDialog.show(baseActivity.getFragmentManager(), FRAGMENT_DIALOG);
        }
    }

    /**
     * 为相机预览创建新的CameraCaptureSession
     */
    private void createCameraPreviewSession(AutoFitTextureView mTextureView, Size mPreviewSize,
                                            CameraDevice mCameraDevice, ImageReader mImageReader, Handler mHandler) {
        try {
            ImageReader.OnImageAvailableListener imageAvailableListener = getListener(bitmapCallBack);
            mImageReader.setOnImageAvailableListener(imageAvailableListener, mHandler);

            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;

            // 将默认缓冲区的大小配置为我们想要的相机预览的大小。 设置分辨率
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

            // 预览的输出Surface。
            Surface previewSurface = new Surface(texture);
            Surface readerSurface = mImageReader.getSurface();

            //设置了一个具有输出Surface的CaptureRequest.Builder。
            final CaptureRequest.Builder mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 这里一定分别add两个surface，一个Textureview的，一个ImageReader的，如果没add，会造成没摄像头预览，或者没有ImageReader的那个回调！！
            mPreviewRequestBuilder.addTarget(previewSurface);
            mPreviewRequestBuilder.addTarget(readerSurface);

            CameraCaptureSession.StateCallback stateCallback = getStateCallback(mPreviewRequestBuilder);
            mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, readerSurface), stateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 检查权限
     */
    private void requestCameraPermission() {
        baseActivity.requestPermission("请给予相机、存储权限，以便app正常工作",
                new BaseActivity.Callback() {
                    @Override
                    public void success() {
                    }

                    @Override
                    public void failed() {
                        Toast.makeText(baseActivity, "未授予相机、存储权限！", Toast.LENGTH_SHORT).show();
                    }
                },
                new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE});
    }

    /**
     * 启动一个HandlerThread
     */
    private void startHandlerThread() {
        mHandlerThread = new HandlerThread("Camera_" + mCameraId);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        mTextureView.setVisibility(View.VISIBLE);
    }

    /**
     * 停止一个HandlerThread
     */
    private void stopHandlerThread() {
        //发送一张全黑图片，以覆盖最后一个图像
        Bitmap bitmap = Bitmap.createBitmap(mImageReader.getWidth(), mImageReader.getHeight(), Bitmap.Config.RGB_565);

        //先关掉图片流，不再往handler里发消息
        mTextureView.setSurfaceTextureListener(null);
        mTextureView.setVisibility(View.INVISIBLE);
        if (mImageReader != null) {
            mImageReader.getSurface().release();
            mImageReader.close();
            mImageReader = null;
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        //重新初始化，如果直接退出有消息还未处理
        mHandler.getLooper().quitSafely();
        if (mHandlerThread != null) {
            // mHandlerThread.quitSafely();
            mHandlerThread = null;
        }
        if (mHandler != null) {
            mHandler = null;
        }
        //bitmap先创建，当时需要最后发送
        bitmap.eraseColor(Color.parseColor("#808080"));//填充颜色
        bitmapCallBack.handleBitmap(bitmap);
        //暂时先这样，不延迟无法覆盖掉最后一张
        baseActivity.runOnUiThread(() -> bitmapCallBack.handleBitmap(bitmap), 10);
        baseActivity.runOnUiThread(() -> bitmapCallBack.handleBitmap(bitmap), 100);
    }

    private CameraCaptureSession.StateCallback getStateCallback(CaptureRequest.Builder mPreviewRequestBuilder) {
        //创建一个CameraCaptureSession来进行相机预览。
        return new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession mCaptureSession) {
                // 相机已经关闭
                if (null == mCameraDevice) {
                    return;
                }
                // 会话准备好后，我们开始显示预览
                try {
                    // 自动对焦应
                    mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    // 最终开启相机预览并添加事件
                    CaptureRequest mPreviewRequest = mPreviewRequestBuilder.build();
                    mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                baseActivity.showToast("Failed");
            }
        };
    }

    //每隔20毫秒接收一次图片回调
    private TimeWhere timeWhere = TimeWhere.start(20);

    private ImageReader.OnImageAvailableListener getListener(BitmapCallBack bitmapCallBack) {
        return reader -> {
            //获取最新的一帧的Image
            Image image = reader.acquireLatestImage();
            if (image == null) {
                return;
            }
            boolean reStart = timeWhere.reStart();
            if (!reStart) {
                image.close();
                return;
            }
            //因为是ImageFormat.JPEG格式，所以 image.getPlanes()返回的数组只有一个，也就是第0个。
            Image.Plane[] planes = image.getPlanes();//此行代码不断输出调式日志：D/qdgralloc: GetYUVPlaneInfo: Invalid format passed: 0x21
            ByteBuffer byteBuffer = planes[0].getBuffer();
            byte[] bytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(bytes);

            baseActivity.runOnUiThread(() -> {
                TimeUtils timeUtils = TimeUtils.start();
                Bitmap temp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null);
                long runTime = timeUtils.endAndGetRunTime();
                // Log.i("decodeByteArray", String.format("处理时间%d ms", runTime));
                bitmapCallBack.handleBitmap(temp);
            }, 0);

            //一定需要close，否则不会收到新的Image回调。
            image.close();
        };
    }

    public interface BitmapCallBack {
        void handleBitmap(Bitmap bitmap);
    }
}
